package core.reports;

import java.io.IOException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import common.TimeSource;

import core.Amount;
import core.Item;
import core.ItemManager;
import core.Product;
import core.ProductGroup;
import core.ProductManager;
import core.RootStorageUnit;
import core.StorageUnit;

/**
 * This implementation of IReportVisitor generates the Product Stats Report
 * @author Royce
 *
 */
public class ProductStatsVisitor implements IReportVisitor {
	private IReportBuilder _builder;
	private TraversingType _type;
	private Calendar _startDate;
	private Calendar _prodCreateDate;
	private List<Calendar> _datesItemsAdded;
	private List<Calendar> _datesItemsRemoved;
	private List<Integer> _dailySupply;
	private int _startNumber;
	private int _curSupply;
	private int _usedsum;
	private int _usedItems;
	private int _usedMax;
	private int _cursum;
	private int _curItems;
	private int _curMax;
	
	public ProductStatsVisitor(IReportBuilder builder, int months) throws IOException {
		if(months < 1 || months > 100){
			throw new IOException("Invalid reporting period");
		}
		_startDate = (Calendar) TimeSource.instance().getTime().clone();
		_startDate.add(Calendar.MONTH, -1*months);
		setToMidnight(_startDate);
		_prodCreateDate = TimeSource.instance().getTime();
		setBuilder(builder);
		_type = TraversingType.POSTORDER;
		_datesItemsAdded = new ArrayList<Calendar>();
		_datesItemsRemoved = new ArrayList<Calendar>();
		_dailySupply = new ArrayList<Integer>();
		_startNumber = 0;
		_curSupply = 0;
		_curMax = 0;
		_curItems = 0;
		_cursum = 0;
		_usedsum = 0;
		_usedMax = 0;
		_usedItems = 0;		
	}

	/**
	 * Sets the report Builder that this visitor will use.
	 * @param builder any implementation of IReportBuilder.
	 * @throws IOException 
	 */
	private void setBuilder(IReportBuilder builder) throws IOException {
		_builder = builder;
		String title = "";
		_builder.startTable(title, 10);
		_builder.addCell("Description");
		_builder.addCell("Barcode");
		_builder.addCell("Size");
		_builder.addCell("3-Month \nSupply");
		_builder.addCell("Supply: \nCur/Avg");
		_builder.addCell("Supply: \nMin/Max");
		_builder.addCell("Supply: \nUsed/Added");
		_builder.addCell("Shelf Life");
		_builder.addCell("Used Age: \nAvg/Max");
		_builder.addCell("Cur Age: \nAvg/Max");
		
	}

	/**
	 * Called when the Root Storage Unit is visited.
	 * @param rsu the RootStorageUnit.
	 */
	@Override
	public void visitRoot(RootStorageUnit rsu) {
		//do nothing
	}

	/**
	 * Called when a Storage Unit is visited
	 * @param su a reference to the visited StorageUnit
	 */
	@Override
	public void visitStorageUnit(StorageUnit su) {
		//do nothing
	}

	/**
	 * Called when a Product Group is visited
	 * @param pg a reference to the visited ProductGroup
	 */
	@Override
	public void visitProductGroup(ProductGroup pg) {
		//do nothing
	}

	/**
	 * Called when a Product is visited
	 * @param product a reference to the visited Product
	 */
	@Override
	public void visitProduct(Product product) {
		try {
			_builder.addCell(product.getDescription());
			_builder.addCell(product.getBarcode());
			_builder.addCell(getAmountString(product.getSize()));
			int supply = product.getThreeMonthSupply();
			_builder.addCell((supply == 0) ? "" : supply);
			//calculate daily supply
			Collections.sort(_datesItemsAdded);
			Collections.sort(_datesItemsRemoved);
			generateDailySupply();
			
			//calculate current supply
			int curSupply = _curSupply;
			//calculate average supply
			float avgSupply = getAverageSupply();
			
			String avgString = Integer.toString(curSupply) + " / " + 
					roundFloat(avgSupply);
			_builder.addCell(avgString);
			
			//calculate minimum supply
			int minSupply = getMinSupply();
			//calculate maximum supply
			int maxSupply = getMaxSupply();
			String minMaxString = Integer.toString(minSupply) + " / " + 
					Integer.toString(maxSupply);
			_builder.addCell(minMaxString);
			
			String addRemoveString = "";
			//calculate number used
			addRemoveString += Integer.toString(_datesItemsRemoved.size());
			addRemoveString += " / ";
			//calculate number added
			addRemoveString += Integer.toString(_datesItemsAdded.size());
			_builder.addCell(addRemoveString);
			
			//get shelf life
			if (product.getShelfLife() > 0)
				_builder.addCell(Integer.toString(product.getShelfLife()) + " months");
			else
				_builder.addCell("");
			//calculate average use age
			float avguse = 0;
			if (_usedItems > 0)
				avguse = (float)_usedsum / (float)_usedItems;
			
			//calculate max use age
			String usedString = roundFloat(avguse) + " days /\n" + Integer.toString(_usedMax) + 
					" days";
			_builder.addCell(usedString);
			//calculate average current age
			float avgcur = 0;
			if (_curItems > 0)
				avgcur = (float)_cursum / (float)_curItems;
			String curString = roundFloat(avgcur) + " days /\n" + Integer.toString(_curMax) + 
					" days";
			_builder.addCell(curString);
			//calculate max current age
			_builder.completeRow();
		} catch (IOException e) {
			//do nothing for now
		}
		finally
		{
			_datesItemsAdded = new ArrayList<Calendar>();
			_datesItemsRemoved = new ArrayList<Calendar>();
			_dailySupply = new ArrayList<Integer>();
			_startNumber = 0;
			_curSupply = 0;
			_curMax = 0;
			_curItems = 0;
			_cursum = 0;
			_usedsum = 0;
			_usedMax = 0;
			_usedItems = 0;
			_prodCreateDate = TimeSource.instance().getTime();
		}
	}

	private String roundFloat(Float f)
	{
		DecimalFormat df = new DecimalFormat("#0.#");
		return  df.format(f);
	}
	
	private void setToMidnight(Calendar c)
	{
		c.set(Calendar.HOUR_OF_DAY, 0);
		c.set(Calendar.MINUTE, 0);
		c.set(Calendar.SECOND, 0);
		c.set(Calendar.MILLISECOND, 0);
	}
	/**
	 * Generate a list of the dates in the reporting period with the number of items in the system
	 * on each day
	 */
	private void generateDailySupply(){
		Calendar _tempStart;
		if (_startDate.after(_prodCreateDate))
		{
			_tempStart = (Calendar)_startDate.clone();
		}
		else
		{
			_tempStart = (Calendar)_prodCreateDate.clone();
			setToMidnight(_tempStart);
		}
		Calendar tempEnd = TimeSource.instance().getTime();
		
		Iterator<Calendar> iterAdd = _datesItemsAdded.iterator();
		Iterator<Calendar> iterRemove = _datesItemsRemoved.iterator();
	
		//set the first arraylist entry to _startNumber
		int curNumber = _startNumber;
		
		Calendar curAdd = null;
		if(iterAdd.hasNext())
			curAdd = iterAdd.next();
		Calendar curRemove = null;
		if(iterRemove.hasNext())
			curRemove = iterRemove.next();
		
		//for each day in the reporting period
		for(Calendar curCal = (Calendar)_tempStart.clone(); 
				!curCal.after(tempEnd); 
				curCal.add(Calendar.DATE, 1)){
			//while the next add date == next date
			while(sameDay(curCal, curAdd)){
				//add 1 to the current arraylist entry
				curNumber++;
				if(!iterAdd.hasNext())break;
				curAdd = iterAdd.next();
			}
			//while the next remove date == next date
			while(sameDay(curCal, curRemove)){
				//subtract 1 from the current arraylist entry
				curNumber--;
				if(!iterRemove.hasNext())break;
				curRemove = iterRemove.next();
			}
			_dailySupply.add(curNumber);
		}
	}
	
	private int getMinSupply(){
		int min = Integer.MAX_VALUE;
		Iterator<Integer> iterSupply = _dailySupply.iterator();
		
		while(iterSupply.hasNext()){
			int curSupply = iterSupply.next();
			min = (curSupply < min)? curSupply : min;
		}
		return min;
	}
	
	private int getMaxSupply(){
		int max = 0;
		Iterator<Integer> iterSupply = _dailySupply.iterator();
		
		while(iterSupply.hasNext()){
			int curSupply = iterSupply.next();
			max = (curSupply > max)? curSupply : max;
		}
		return max;
	}
	
	private float getAverageSupply(){
		int days = 0;
		int total = 0;
		
		Iterator<Integer> iterSupply = _dailySupply.iterator();
		
		while(iterSupply.hasNext()){
			days++;
			total += iterSupply.next();
		}
		return (float)total / (float)days;
	}
	
	 
	
	/**
	 * @param cal1
	 * @param cal2
	 * @return whether the two Calendar objects are the same day
	 */
	private boolean sameDay(Calendar cal1, Calendar cal2){
		if(cal1 == null || cal2 == null)return false;
		return cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA) &&
				cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
                cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR);
	}
	
	private String getAmountString(Amount amt)
	{
		DecimalFormat df = new DecimalFormat("#0.##");
		String theString = df.format(amt.getQuantity()) + " " + amt.getUnit();
		return theString;
	}
	
	/**
	 * Called when an Item is visited
	 * @param item a reference to the visited Item
	 */
	@Override
	public void visitItem(Item item) {
		if (_prodCreateDate.after(item.getEntryDate()))
		{
			_prodCreateDate = (Calendar) item.getEntryDate().clone();
		}
		if (item.isUsed())
		{
			if (_startDate.before(item.getEntryDate()) || _startDate.equals(item.getEntryDate()))
			{
				_datesItemsAdded.add(item.getEntryDate());
				
			}
			else{
				_startNumber++;
			}
			if(_startDate.before(item.getExitDate()) || _startDate.equals(item.getExitDate()))
			{
				_datesItemsRemoved.add(item.getExitDate());
				_usedsum += item.age();
				_usedItems ++;
				if (item.age() > _usedMax)
					_usedMax = item.age();
			}
			else{
				_startNumber --;
			}
			
		}
		else
		{
			//if (_startDate.compareTo(item.getEntryDate()) < 0)
			if(_startDate.before(item.getEntryDate()) || _startDate.equals(item.getEntryDate()))
			{
				_datesItemsAdded.add(item.getEntryDate());
			}
			else
			{
				_startNumber++;
			}
			_curSupply++;
			_cursum += item.age();
			_curItems ++;
			if (item.age() > _curMax)
				_curMax = item.age();
		}
	}
	
	@Override
	public TraversingType getType() {
		return _type;
	}

	@Override
	public void visitProductManager(ProductManager pm) {
		//do nothing
	}

	@Override
	public void visitItemManager(ItemManager im) {
		//do nothing
	}

	@Override
	public void onDone() {
		// TODO Auto-generated method stub
		
	}
}
